/////////////////////////////////////////////////////////////////////
//
//  MOE Framework (Meta Object Encapsulation Framework)
//
//  Original Author: Wiksy Wang, 2011
//
/////////////////////////////////////////////////////////////////////

#ifndef MOE_PERSIST_PROPERTY_TREE_WRITER_HPP
#define MOE_PERSIST_PROPERTY_TREE_WRITER_HPP

#include <boost/scoped_array.hpp>
#include <boost/property_tree/ptree_fwd.hpp>

#include "DefaultNamespaceTreeFormat.hpp"
#include "../ClassMeta.h"
#include "../DataMemberMeta.h"
#include "../ObjectTypeManager.h"
#include "../Name/Namespace.h"

namespace MOE
{

class ClassMeta;
class DataMemberMeta;

namespace Persist
{

template <class TreeType, template <class> class TreeFormatPolicy = DefaultNamespaceTreeFormat>
class PropertyTreeWriter
{
protected:
	struct PropertyTreeWriterVisitor
	{
		PropertyTreeWriterVisitor(void* data, TreeType& outTree, const Name::Namespace* targetSpace)
			: _data(data)
			, _outTree(outTree)
			, _targetNamespace(targetSpace)
			, _success(true)
		{
		}

		void operator()(const RawByteVector& name, const DataMemberMeta& memberMeta)
		{
			ClassMetaProxy metaProxy = ObjectTypeManager::GetSingleton().GetClassMeta(memberMeta.GetTypeID());
			if (!metaProxy.IsValid())
			{
				return;
			}
			const ClassMeta& meta = metaProxy;

			switch (memberMeta.GetDerivedType())
			{
			case DT_Value:
				_success &= WriteValue(name, memberMeta, meta);
				break;
			case DT_Pointer:
				_success &= WritePointer(name, memberMeta, meta);
				break;
			default:
				_success = false;
			}
		}

		void* _data;
		TreeType& _outTree;
		const Name::Namespace* _targetNamespace;
		bool _success;

	private:
		bool WriteValue(const RawByteVector& name, const DataMemberMeta& memberMeta, const ClassMeta& classMeta) const
		{
			bool writeResult = false;

			if (memberMeta.HasDirectAccess())
			{
				writeResult = PropertyTreeWriter().Write(classMeta, memberMeta.DirectAccess(_data), _outTree.add_child(name.to_string<TreeType::data_type>(), TreeType()));
			}
			else if (memberMeta.HasGetter())
			{
				Address tempData = classMeta.New();
				memberMeta.Get(_data, tempData);
				writeResult = PropertyTreeWriter().Write(classMeta, tempData, _outTree.add_child(name.to_string<TreeType::data_type>(), TreeType()));
				classMeta.Delete(tempData);
			}

			return writeResult;
		}

		bool WritePointer(const RawByteVector& name, const DataMemberMeta& memberMeta, const ClassMeta& classMeta) const
		{
			bool writeResult = false;

			Address memberData;
			memberMeta.Get(_data, &memberData);
			if (memberData)
			{
				const RawByteVector& objFullName = Name::Namespace::GetObjectName(memberData);
				if (!objFullName.empty())
				{
					RawByteVector objPathName = _targetNamespace->GetRelativePath(Name::NamePath(objFullName, sizeof(TreeType::data_type::value_type)));
					_outTree.put(name.to_string<TreeType::data_type>(), objPathName.to_string<TreeType::data_type>());
				}
			}

			return writeResult;
		}
	};

public:
	explicit PropertyTreeWriter(const Name::Namespace* targetNamespace = NULL)
		: _targetNamespace(targetNamespace)
	{
		if (_targetNamespace == NULL)
		{
			_targetNamespace = &Name::Namespace::GetGlobalNamespace();
		}
	}

	bool Write(const ClassMeta& meta, void* data, TreeType& outTree) const
	{
		if (meta.IsAtomClass())
		{
			return WriteAtom(meta, data, outTree);
		}

		PropertyTreeWriterVisitor visitor(data, outTree, _targetNamespace);
		meta.Visit(visitor);
		return visitor._success;
	}

protected:
	bool WriteAtom(const ClassMeta& meta, void* data, TreeType& outTree) const
	{
		switch (meta.GetAtomType())
		{
		case AT_Integer:
			outTree.put_value<int>(*static_cast<int*>(data));
			return true;
		case AT_Float:
			outTree.put_value<float>(*static_cast<float*>(data));
			return true;
		case AT_Bool:
			outTree.put_value<bool>(*static_cast<bool*>(data));
			return true;
		case AT_String:
			{
				std::string& tmpStr = *static_cast<std::string*>(data);
				outTree.put_value<TreeType::data_type>(TreeType::data_type(tmpStr.begin(), tmpStr.end()));
			}
			return true;
		case AT_WideString:
			{
				std::wstring& tmpStr = *static_cast<std::wstring*>(data);
				outTree.put_value<TreeType::data_type>(TreeType::data_type(tmpStr.begin(), tmpStr.end()));
			}
			return true;
		case AT_NonAtom:
		default:
			return false;
		}
	}

protected:
	const Name::Namespace* _targetNamespace;
};

}

}

#endif
